/* See LICENSE file for license details. */
#define _XOPEN_SOURCE 500
#if HAVE_SHADOW_H
#include <shadow.h>
#endif

#include <ctype.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <X11/keysym.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/dpms.h>

#if HAVE_BSD_AUTH
#include <login_cap.h>
#include <bsd_auth.h>
#endif

static void die(const char *errstr, ...) {
    va_list ap;

    va_start(ap, errstr);
    vfprintf(stderr, errstr, ap);
    va_end(ap);
    exit(EXIT_FAILURE);
}

#ifndef HAVE_BSD_AUTH
static const char * get_password() { /* only run as root */
    const char *rval;
    struct passwd *pw;

    if(geteuid() != 0)
        die("slock-ng: cannot retrieve password entry (make sure to suid slock)\n");
    pw = getpwuid(getuid());
    endpwent();
    rval =  pw->pw_passwd;

#if HAVE_SHADOW_H
    {
        struct spwd *sp;
        sp = getspnam(getenv("USER"));
        endspent();
        rval = sp->sp_pwdp;
    }
#endif

    /* drop privileges */
    if(setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0)
        die("slock-ng: cannot drop privileges\n");
    return rval;
}
#endif

int main(int argc, char **argv) {
    char curs[] = {0, 0, 0, 0, 0, 0, 0, 0};
    char buf[32], passwd[256];
    int num, screen;

#ifndef HAVE_BSD_AUTH
    const char *pws;
#endif
    unsigned int len;
    Bool running = True;
    Cursor invisible;
    Display *dpy;
    KeySym ksym;
    Pixmap pmap;
    Window root, w;
    XColor black, dummy;
    XEvent ev;
    XSetWindowAttributes wa;

    if((argc == 2) && !strcmp("-v", argv[1]))
        die("slock-ng-"VERSION", © 2006-2008 Anselm R Garbe; \
2010 Andrienko E.A.\n");
    else if(argc != 1)
        die("usage: slock-ng [-v]\n");

#ifndef HAVE_BSD_AUTH
    pws = get_password();
#endif

    if(!(dpy = XOpenDisplay(0)))
        die("slock-ng: cannot open display\n");
    XBell(dpy, 100);
    screen = DefaultScreen(dpy);
    root = RootWindow(dpy, screen);

    /* init */
    wa.override_redirect = 1;
    wa.background_pixel = BlackPixel(dpy, screen);
    w = XCreateWindow(dpy, root, 0, 0, DisplayWidth(dpy, screen), 
            DisplayHeight(dpy, screen), 0, DefaultDepth(dpy, screen), 
            CopyFromParent, DefaultVisual(dpy, screen), 
            CWOverrideRedirect | CWBackPixel, &wa);
    XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "black", &black, 
            &dummy);
    pmap = XCreateBitmapFromData(dpy, w, curs, 8, 8);
    invisible = XCreatePixmapCursor(dpy, pmap, pmap, &black, &black, 0, 0);
    XDefineCursor(dpy, w, invisible);
    XMapRaised(dpy, w);
    sleep(1);
    DPMSEnable(dpy);
    DPMSForceLevel(dpy, DPMSModeOff);
    for(len = 1000; len; len--) {
        if(XGrabPointer(dpy, root, False, 
                    ButtonPressMask | ButtonReleaseMask | PointerMotionMask, 
                    GrabModeAsync, GrabModeAsync, None, invisible, 
                    CurrentTime) == GrabSuccess) {
            break;
        }
        usleep(1000);
    }
    if((running = running && (len > 0))) {
        for(len = 1000; len; len--) {
            if(XGrabKeyboard(dpy, root, True, GrabModeAsync, GrabModeAsync, 
                        CurrentTime) == GrabSuccess) {
                break;
            }
            usleep(1000);
        }
        running = (len > 0);
    }
    len = 0;
    XSync(dpy, False);

    /* main event loop */
    while (running && !XNextEvent(dpy, &ev)) {
        if (len == 0 && DPMSCapable(dpy)) {
            DPMSEnable(dpy);
            DPMSForceLevel(dpy, DPMSModeOff);
        }
        if (ev.type == KeyPress) {
            buf[0] = 0;
            num = XLookupString(&ev.xkey, buf, sizeof buf, &ksym, 0);
            if (IsKeypadKey(ksym)) {
                if (ksym == XK_KP_Enter)
                    ksym = XK_Return;
                else if (ksym >= XK_KP_0 && ksym <= XK_KP_9)
                    ksym = (ksym - XK_KP_0) + XK_0;
            }
            if (IsFunctionKey(ksym) || IsKeypadKey(ksym)
                    || IsMiscFunctionKey(ksym) || IsPFKey(ksym)
                    || IsPrivateKeypadKey(ksym))
                continue;
            switch (ksym) {
                case XK_Return:
                    passwd[len] = 0;
                    #ifdef HAVE_BSD_AUTH
                    running = !auth_userokay(getlogin(), NULL, "auth-xlock", 
                            passwd);
                    #else
                    running = strcmp(crypt(passwd, pws), pws);
                    #endif
                    if (running != 0)
                        XBell(dpy, 100);
                    len = 0;
                    break;
                case XK_Escape:
                    len = 0;
                    break;
                case XK_BackSpace:
                    if(len)
                        --len;
                    break;
                default:
                    if (num && !iscntrl((int) buf[0]) && 
                            (len + num < sizeof passwd)) {
                        memcpy(passwd + len, buf, num);
                        len += num;
                    }
                    break;
            }
        }
    }

    XUngrabPointer(dpy, CurrentTime);
    XFreePixmap(dpy, pmap);
    XDestroyWindow(dpy, w);
    XCloseDisplay(dpy);

    return 0;
}

